<?php
namespace app\api\controller\v1;

use app\api\model\v1\Token AS TokenModel;
use app\api\model\v1\Account AS AccountModel;

class Secure extends Base
{
    private $expires = 7200;
    private $promptDiff = 60;
    private $fixedParams = [
        'project' => 'yunjing',
        'company' => 'yunxiang',
        'location' => 'shandong'
    ];
    // company=yunxiang&location=shandong&nonce=123456&project=yunjing&timestamp=15512345678
    // Y29TCGFUET15DW54AWFUZYZSB2NHDGLVBJ1ZAGFUZG9UZYZUB25JZT0XMJM0NTYMCHJVAMVJDD15DW5QAW5NJNRPBWVZDGFTCD0XNTUXMJM0NTY3OA==

    // 生成 signature 时要求两个参数，分别是 nonce、timestamp；
    // 生成 token 时要求三个参数，分别是 nonce、timestamp、signature；
    private function _getCommon() {
        $args = func_get_args();
        $suffix = ['nonce' => $args[0], 'timestamp' => $args[1]];
        if (count($args) === 3) {
            $suffix['signature'] = $args[2];
        }
        $arr = array_merge($this->fixedParams, $suffix);
        ksort($arr);
        $urlParams = [];
        array_walk($arr, function($value, $key) use (&$urlParams) {
            array_push($urlParams, $key . '=' . $value);
        });
        return $urlParams;
    }

    private function _getSignatureBase64($nonce, $timestamp) {
        return base64_encode(implode('&', $this->_getCommon($nonce, $timestamp)));
    }

    private function _getSignatureBase64UpperCase($nonce, $timestamp) {
        return strtoupper($this->_getSignatureBase64($nonce, $timestamp));
    }

    // 后来发现使用base64 编码，结果太长了，达到了284个字符。
    /*
    private function _getTokenBase64($nonce, $timestamp, $signature) {
        \think\Db::name('test')->insert(['content'=>json_encode(func_get_args())]);
        return base64_encode(implode('&', $this->_getCommon($nonce, $timestamp, $signature)));
    }

    private function _getTokenBase64UpperCase($nonce, $timestamp, $signature) {
        return strtoupper($this->_getTokenBase64($nonce, $timestamp, $signature));
    }
    */
   
    // 所以改为使用 sha1 编码的方式得到 token，结果只有40 个字符。
    private function _getTokenSha1($nonce, $timestamp, $signature) {
        return sha1(implode('&', $this->_getCommon($nonce, $timestamp, $signature)));
    }

    private function _getTokenSha1UpperCase($nonce, $timestamp, $signature) {
        return strtoupper($this->_getTokenSha1($nonce, $timestamp, $signature));
    }

    private function _getToken($nonce, $timestamp, $signature) {
        return [
            'expires' => $this->expires,
            'token' => $this->_getTokenSha1UpperCase($nonce, $timestamp, $signature),
            'token_time' => \jm\helper\DateTime::YMDHISWithLine()
        ];
    }

    // 参数中的 authtype 只是用于迷惑抓包者，不用于计算signature 和 token；
    // 验证 signature，生成 token 给客户端；
    public function secure() {
        try {
            // 注册前获取token，此时还没有account_id；
            // 假如用户退出App，清理本地信息，导致token和account_id都没有了，这和第一种情况一样了；
            // 上面两个情况，都应该在登录时删除旧token，更新新token到用户表；
            // 当使用过程中，token过期了，获取新token，删除旧token--此时需要account_id，而且一定存在，直接更新用户表；
            $param = $this->jmParameter(['account_id', 'nonce', 'timestamp', 'authtype', 'signature']);
            $validateResult = $this->validate(
                $param, 
                [
                    'nonce' => 'require',
                    'timestamp' => 'require',
                    'authtype' => ['require', 'regex' => '/^\d$/'],
                    'signature' => 'require'
                ]
            );
            if (true !== $validateResult) {
                return $this->jmReturnJson(4001, $validateResult);
            }

            $serverSignature = $this->_getSignatureBase64UpperCase($param['nonce'], $param['timestamp']);
            if ($param['signature'] !== $serverSignature) {
                return $this->jmReturnJson(4101, '签名无效');
            }

            // 分 session 和 token 两种方式；
            // $secureType = \think\Config::get('ifmode.glhf_secure_type');
            // if ($secureType === 'token') {
                $tokenInfo = $this->_getToken($param['nonce'], $param['timestamp'], $param['signature']);

                // 新增token；token_time 是生成时间 2019-05-20 17:56:47。
                $insertResult = TokenModel::insert(['token' => $tokenInfo['token'], 'token_time' => $tokenInfo['token_time']]);
                if (empty($insertResult)) {
                    return $this->jmReturnJson(4102, 'token保存失败，请重试');
                }
                
                // 删除旧token
                if (!empty($param['account_id']) ) {
                    $account = AccountModel::field(['id','token'])->find($param['account_id'] * 1);
                    // 测试期间，遇到：message=异常：Trying to get property of non-object。
                    // 所以应该做个判断；
                    if (!is_null($account)) {
                        $oldToken = $account->token;
                        $account->token = $tokenInfo['token'];
                        $isSuccess = $account->isUpdate(true)->save();
                        if ($isSuccess === 0) {
                            return $this->jmReturnJson(4002, '操作失败');
                        }

                        // 更新成功，并且有旧的token 则删除；
                        if (!empty($oldToken)) {
                            // 真实删除，因为结果无关紧要，可以不做判断；
                            TokenModel::where('token', $oldToken)->delete();
                        }
                    }                        
                }

                $tokenInfo['token_time'] = strtotime($tokenInfo['token_time']);
                return $this->jmReturnJson(200, '操作成功', $tokenInfo);
            // } else {
            //     \think\Session::set('rand', \jm\helper\Random::rand(8));
            //     return $this->jmReturnJson(200, '操作成功', ['expires' => \think\Config::get('cookie.expire')]);
            // }
        } catch (\Exception $e) {
            return $this->jmException($e);
        }
    }
}